Dplyr is a grammar for data manipulation. It combines various steps of manipulation in one chunk of code without nesting.

For more information check the official webside.

Tidyverse

The package is part of the “tidyvers-Universe”. Tidyverse is a a collection of packages which share a common philosophy of data analysis and R programming and are designed to work together seamlessly.

They share common data representation and API design and the tidyverse package allows you to install an update them as a set (they update frequently).

Introduction

This tutorial will introduce the basic commands to work with dplyr. This cheat sheetby R-Studio gives a overview of more helpful commands.

The functions work in a “regular” way and in the %>%-version.

or

As the author of this tutorial likes the %>% a lot, most functions will be shown in this way. The shortcut for ‘%>%’ is strg+command+m


Let’s get started. First you have to install and load the the tidyverse package (or dplyr and ggplot2 if you want to do it separatly).

library(tidyverse)
#we use the diamonds-data 

select()

selects a set of columns

#Classic Style
select(diamonds, cut, color) 
#pipe-style
diamonds %>% select(cut,color) 
#adding the comand head at the end, to only select the fist five rows
diamonds %>% select(cut, color) %>% head
#select all rows staring with "c" 
diamonds %>% select(starts_with('c'),price)
#Also works with ends_with(), contains(), matches(),...

filter

You can filter by the values of one or more columns.

#filter by one column
diamonds %>% filter(color=='E')
diamonds %>% filter(price>350)
#the %in%-command is usefull to filter on several values
diamonds %>% filter(color %in% c('E','F'))
#The ! in front of the command changes the operator to "not in"
diamonds %>% filter(!color %in% c('E','F')) 
#filter on more than one column

#The "AND"-combination can be done in two ways
diamonds %>% filter(color=='E' , price>350) 
#diamonds %>% filter(color=='E' & price>350)
#has the same output
#filtering with "OR" on two columns
diamonds %>% filter(color=='E' | price>350)

arrange

reorder the rows by one or more columns

#reorder by the column "cut"
diamonds %>% arrange(cut)
#reorder by the column cut and price. To order in descending way, put the column in desc()
diamonds %>% arrange(cut,desc(price)) 

mutate

you can create new columns via mutate

#simple new column
diamonds %>% select(cut, color, price) %>% 
              mutate(price_n = price*1000)
#new column base on a case-statement
diamonds %>% select(cut, color, price) %>% 
              mutate(cut_n = case_when(.$cut=='Ideal' ~ '1',
                                      .$cut=='Premium' ~ '2',
                                      .$cut=='Very Good' ~ '3',
                                      .$cut=='Good' ~ '4',
                                      .$cut=='Fair' ~ '5',
                                      TRUE ~ '9'))
#if you miss a value, it is going to be NA
# the TRUE-statement ist kind of like a "else"-statement

summarise

You can use all kind of functions to summarize the data (sum, mean, median, …..).

#calculating the mean of the column price
diamonds %>% summarise(mean(price))
#adding the number distinct values of the column cut and the number of rows in the dataframe
diamonds %>% summarise(mean_price=mean(price),
                       num_cut=n_distinct(cut),
                       num_row=n())

group_by

group_by builds groups by the defined columns and does the operations within those groups. For those familiar with sql it works pretty much the same as “group by”

#Group by one column and then summarising with those groups
diamonds %>% group_by(cut) %>% summarise(mean_price=mean(price))

piping comands together

So fare the commands mostly just looked different to “regular” r-commands. Maybe are more straight forward. But to experience (IMO) the real advantage of the dplyr-package, you have to start piping the commands.

# Select all diamonds with a fair cut and then calucate the mean price depending on the clarity
diamonds %>% filter(cut=='Fair') %>% group_by(clarity) %>% summarise(mean_price=mean(price))

some more helpfull functions

cast / melt -> functions of the reshaping2 package

If you don’t know the functions, check them out. They really help to reorganize data.
in this context (tidyvers-universe) they are called spread and gather and are part of the package “tidyr”

#spread the result 

diamonds %>% 
  filter(cut!='Fair') %>% 
  group_by(cut,clarity) %>% 
  summarise(mean_price=mean(price)) %>% 
  spread(key=cut,value=mean_price)
#reversing the spread with gather
diamonds %>% 
  filter(cut!='Fair') %>% 
  group_by(cut,clarity) %>% 
  summarise(mean_price=mean(price)) %>% 
  spread(key=cut,value=mean_price)  %>% 
  gather(cut,price, Good:Ideal )

show the entire data.frame

dplyr always shows the first 10 rows, but sometimes you just want to see them all

#to see the result in the console, use print(n=nrow(.))
diamonds %>% 
  filter(cut!='Fair') %>% 
  group_by(cut,clarity) %>% 
  summarise(mean_price=mean(price)) %>% 
  spread(key=cut,value=mean_price)  %>% 
  gather(cut,price, Good:Ideal ) %>% 
  print(n = nrow(.))
# A tibble: 32 × 3
   clarity       cut    price
     <ord>     <chr>    <dbl>
1       I1      Good 3596.635
2      SI2      Good 4580.261
3      SI1      Good 3689.533
4      VS2      Good 4262.236
5      VS1      Good 3801.446
6     VVS2      Good 3079.108
7     VVS1      Good 2254.774
8       IF      Good 4098.324
9       I1 Very Good 4078.226
10     SI2 Very Good 4988.688
11     SI1 Very Good 3932.391
12     VS2 Very Good 4215.760
13     VS1 Very Good 3805.353
14    VVS2 Very Good 3037.765
15    VVS1 Very Good 2459.441
16      IF Very Good 4396.216
17      I1   Premium 3947.332
18     SI2   Premium 5545.937
19     SI1   Premium 4455.269
20     VS2   Premium 4550.331
21     VS1   Premium 4485.462
22    VVS2   Premium 3795.123
23    VVS1   Premium 2831.206
24      IF   Premium 3856.143
25      I1     Ideal 4335.726
26     SI2     Ideal 4755.953
27     SI1     Ideal 3752.118
28     VS2     Ideal 3284.550
29     VS1     Ideal 3489.744
30    VVS2     Ideal 3250.290
31    VVS1     Ideal 2468.129
32      IF     Ideal 2272.913
#if you use View() RStudio opens a new tab with the data
diamonds %>% 
  filter(cut!='Fair') %>% 
  group_by(cut,clarity) %>% 
  summarise(mean_price=mean(price)) %>% 
  spread(key=cut,value=mean_price)  %>% 
  gather(cut,price, Good:Ideal ) %>% 
  View()

some helpfull other tutorials

Have fun exploring your data!

LS0tDQp0aXRsZTogImRwbHlyIHR1dG9yaWFsIg0Kb3V0cHV0Og0KICAjaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KICAjcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIG91dHB1dDogaHRtbF9ub3RlYm9vaw0KdXJsY29sb3I6IGJsdWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpEcGx5ciBpcyBhIGdyYW1tYXIgZm9yIGRhdGEgbWFuaXB1bGF0aW9uLiBJdCBjb21iaW5lcyB2YXJpb3VzIHN0ZXBzIG9mIG1hbmlwdWxhdGlvbiBpbiBvbmUgY2h1bmsgb2YgY29kZSB3aXRob3V0IG5lc3RpbmcuIA0KDQpGb3IgbW9yZSBpbmZvcm1hdGlvbiBjaGVjayB0aGUgb2ZmaWNpYWwgW3dlYnNpZGVdKGh0dHA6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykuDQoNCiMjVGlkeXZlcnNlDQpUaGUgcGFja2FnZSBpcyBwYXJ0IG9mIHRoZSAidGlkeXZlcnMtVW5pdmVyc2UiLiANClRpZHl2ZXJzZSBpcyBhIGEgY29sbGVjdGlvbiBvZiBwYWNrYWdlcyB3aGljaCBzaGFyZSBhIGNvbW1vbiBwaGlsb3NvcGh5IG9mIGRhdGEgYW5hbHlzaXMgYW5kIFIgcHJvZ3JhbW1pbmcgYW5kIGFyZSBkZXNpZ25lZCB0byB3b3JrIHRvZ2V0aGVyIHNlYW1sZXNzbHkuDQoNCi0gZ2dwbG90MiwgZm9yIGRhdGEgdmlzdWFsaXNhdGlvbi4NCi0gZHBseXIsIGZvciBkYXRhIG1hbmlwdWxhdGlvbi4NCi0gdGlkeXIsIGZvciBkYXRhIHRpZHlpbmcuDQotIHJlYWRyLCBmb3IgZGF0YSBpbXBvcnQuDQotIHB1cnJyLCBmb3IgZnVuY3Rpb25hbCBwcm9ncmFtbWluZy4NCi0gdGliYmxlLCBmb3IgdGliYmxlcywgYSBtb2Rlcm4gcmUtaW1hZ2luaW5nIG9mIGRhdGEgZnJhbWVzLg0KDQpUaGV5IHNoYXJlIGNvbW1vbiBkYXRhIHJlcHJlc2VudGF0aW9uIGFuZCBBUEkgZGVzaWduIGFuZCB0aGUgdGlkeXZlcnNlIHBhY2thZ2UgYWxsb3dzIHlvdSB0byBpbnN0YWxsIGFuIHVwZGF0ZSB0aGVtIGFzIGEgc2V0ICh0aGV5IHVwZGF0ZSBmcmVxdWVudGx5KS4NCg0KDQojI0ludHJvZHVjdGlvbg0KVGhpcyB0dXRvcmlhbCB3aWxsIGludHJvZHVjZSB0aGUgYmFzaWMgY29tbWFuZHMgdG8gd29yayB3aXRoIGRwbHlyLg0KVGhpcyBbY2hlYXQgc2hlZXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvc291cmNlL3BkZnMvZGF0YS10cmFuc2Zvcm1hdGlvbi1jaGVhdHNoZWV0LnBkZilieSBSLVN0dWRpbyBnaXZlcyBhIG92ZXJ2aWV3IG9mIG1vcmUgaGVscGZ1bCBjb21tYW5kcy4NCg0KVGhlIGZ1bmN0aW9ucyB3b3JrIGluIGEgInJlZ3VsYXIiIHdheSBhbmQgaW4gdGhlICU+JS12ZXJzaW9uLiANCg0KICArIGZ1bmN0aW9uKGRhdGEsLi4uLikNCg0Kb3INCg0KICArIGRhdGEgJT4lIGZ1bmN0aW9uKC4uLi4pLg0KDQpBcyB0aGUgYXV0aG9yIG9mIHRoaXMgdHV0b3JpYWwgbGlrZXMgdGhlICU+JSBhIGxvdCwgbW9zdCBmdW5jdGlvbnMgd2lsbCBiZSBzaG93biBpbiB0aGlzIHdheS4NClRoZSBzaG9ydGN1dCBmb3IgJyAlPiUgJyBpcyBzdHJnK2NvbW1hbmQrbSAgDQo8YnI+DQo8YnI+DQpMZXQncyBnZXQgc3RhcnRlZC4gRmlyc3QgeW91IGhhdmUgdG8gaW5zdGFsbCBhbmQgbG9hZCB0aGUgdGhlIHRpZHl2ZXJzZSBwYWNrYWdlIChvciBkcGx5ciBhbmQgZ2dwbG90MiBpZiB5b3Ugd2FudCB0byBkbyBpdCBzZXBhcmF0bHkpLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KI3dlIHVzZSB0aGUgZGlhbW9uZHMtZGF0YSANCmBgYA0KDQoNCiMjc2VsZWN0KCkNCnNlbGVjdHMgYSBzZXQgb2YgY29sdW1ucw0KDQpgYGB7cn0NCg0KI0NsYXNzaWMgU3R5bGUNCnNlbGVjdChkaWFtb25kcywgY3V0LCBjb2xvcikgDQoNCmBgYA0KYGBge3J9DQojcGlwZS1zdHlsZQ0KZGlhbW9uZHMgJT4lIHNlbGVjdChjdXQsY29sb3IpIA0KYGBgDQoNCmBgYHtyfQ0KI2FkZGluZyB0aGUgY29tYW5kIGhlYWQgYXQgdGhlIGVuZCwgdG8gb25seSBzZWxlY3QgdGhlIGZpc3QgZml2ZSByb3dzDQpkaWFtb25kcyAlPiUgc2VsZWN0KGN1dCwgY29sb3IpICU+JSBoZWFkDQpgYGANCg0KYGBge3J9DQojc2VsZWN0IGFsbCByb3dzIHN0YXJpbmcgd2l0aCAiYyIgDQpkaWFtb25kcyAlPiUgc2VsZWN0KHN0YXJ0c193aXRoKCdjJykscHJpY2UpDQoNCiNBbHNvIHdvcmtzIHdpdGggZW5kc193aXRoKCksIGNvbnRhaW5zKCksIG1hdGNoZXMoKSwuLi4NCmBgYA0KDQojZmlsdGVyDQpZb3UgY2FuIGZpbHRlciBieSB0aGUgdmFsdWVzIG9mIG9uZSBvciBtb3JlIGNvbHVtbnMuDQoNCmBgYHtyfQ0KI2ZpbHRlciBieSBvbmUgY29sdW1uDQpkaWFtb25kcyAlPiUgZmlsdGVyKGNvbG9yPT0nRScpDQpkaWFtb25kcyAlPiUgZmlsdGVyKHByaWNlPjM1MCkNCmBgYA0KDQpgYGB7cn0NCiN0aGUgJWluJS1jb21tYW5kIGlzIHVzZWZ1bGwgdG8gZmlsdGVyIG9uIHNldmVyYWwgdmFsdWVzDQpkaWFtb25kcyAlPiUgZmlsdGVyKGNvbG9yICVpbiUgYygnRScsJ0YnKSkNCmBgYA0KDQpgYGB7cn0NCiNUaGUgISBpbiBmcm9udCBvZiB0aGUgY29tbWFuZCBjaGFuZ2VzIHRoZSBvcGVyYXRvciB0byAibm90IGluIg0KZGlhbW9uZHMgJT4lIGZpbHRlcighY29sb3IgJWluJSBjKCdFJywnRicpKSANCmBgYA0KDQpgYGB7cn0NCiNmaWx0ZXIgb24gbW9yZSB0aGFuIG9uZSBjb2x1bW4NCg0KI1RoZSAiQU5EIi1jb21iaW5hdGlvbiBjYW4gYmUgZG9uZSBpbiB0d28gd2F5cw0KZGlhbW9uZHMgJT4lIGZpbHRlcihjb2xvcj09J0UnICwgcHJpY2U+MzUwKSANCg0KI2RpYW1vbmRzICU+JSBmaWx0ZXIoY29sb3I9PSdFJyAmIHByaWNlPjM1MCkNCiNoYXMgdGhlIHNhbWUgb3V0cHV0DQoNCmBgYA0KDQpgYGB7cn0NCiNmaWx0ZXJpbmcgd2l0aCAiT1IiIG9uIHR3byBjb2x1bW5zDQpkaWFtb25kcyAlPiUgZmlsdGVyKGNvbG9yPT0nRScgfCBwcmljZT4zNTApDQoNCg0KYGBgDQoNCiNhcnJhbmdlIA0KcmVvcmRlciB0aGUgcm93cyBieSBvbmUgb3IgbW9yZSBjb2x1bW5zDQpgYGB7cn0NCiNyZW9yZGVyIGJ5IHRoZSBjb2x1bW4gImN1dCINCmRpYW1vbmRzICU+JSBhcnJhbmdlKGN1dCkNCmBgYA0KDQpgYGB7cn0NCg0KI3Jlb3JkZXIgYnkgdGhlIGNvbHVtbiBjdXQgYW5kIHByaWNlLiBUbyBvcmRlciBpbiBkZXNjZW5kaW5nIHdheSwgcHV0IHRoZSBjb2x1bW4gaW4gZGVzYygpDQpkaWFtb25kcyAlPiUgYXJyYW5nZShjdXQsZGVzYyhwcmljZSkpIA0KDQoNCmBgYA0KI211dGF0ZSANCnlvdSBjYW4gY3JlYXRlIG5ldyBjb2x1bW5zIHZpYSBtdXRhdGUNCg0KYGBge3J9DQojc2ltcGxlIG5ldyBjb2x1bW4NCmRpYW1vbmRzICU+JSBzZWxlY3QoY3V0LCBjb2xvciwgcHJpY2UpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKHByaWNlX24gPSBwcmljZSoxMDAwKQ0KYGBgDQoNCmBgYHtyfQ0KDQojbmV3IGNvbHVtbiBiYXNlIG9uIGEgY2FzZS1zdGF0ZW1lbnQNCmRpYW1vbmRzICU+JSBzZWxlY3QoY3V0LCBjb2xvciwgcHJpY2UpICU+JSANCiAgICAgICAgICAgICAgbXV0YXRlKGN1dF9uID0gY2FzZV93aGVuKC4kY3V0PT0nSWRlYWwnIH4gJzEnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJGN1dD09J1ByZW1pdW0nIH4gJzInLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuJGN1dD09J1ZlcnkgR29vZCcgfiAnMycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4kY3V0PT0nR29vZCcgfiAnNCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4kY3V0PT0nRmFpcicgfiAnNScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAnOScpKQ0KI2lmIHlvdSBtaXNzIGEgdmFsdWUsIGl0IGlzIGdvaW5nIHRvIGJlIE5BDQojIHRoZSBUUlVFLXN0YXRlbWVudCBpc3Qga2luZCBvZiBsaWtlIGEgImVsc2UiLXN0YXRlbWVudA0KDQpgYGANCiNzdW1tYXJpc2UgDQoNCllvdSBjYW4gdXNlIGFsbCBraW5kIG9mIGZ1bmN0aW9ucyB0byBzdW1tYXJpemUgdGhlIGRhdGEgKHN1bSwgbWVhbiwgbWVkaWFuLCAuLi4uLikuIA0KDQpgYGB7cn0NCiNjYWxjdWxhdGluZyB0aGUgbWVhbiBvZiB0aGUgY29sdW1uIHByaWNlDQpkaWFtb25kcyAlPiUgc3VtbWFyaXNlKG1lYW4ocHJpY2UpKQ0KYGBgDQoNCmBgYHtyfQ0KDQojYWRkaW5nIHRoZSBudW1iZXIgZGlzdGluY3QgdmFsdWVzIG9mIHRoZSBjb2x1bW4gY3V0IGFuZCB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gdGhlIGRhdGFmcmFtZQ0KZGlhbW9uZHMgJT4lIHN1bW1hcmlzZShtZWFuX3ByaWNlPW1lYW4ocHJpY2UpLA0KICAgICAgICAgICAgICAgICAgICAgICBudW1fY3V0PW5fZGlzdGluY3QoY3V0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgbnVtX3Jvdz1uKCkpDQoNCmBgYA0KDQojZ3JvdXBfYnkgDQpncm91cF9ieSBidWlsZHMgZ3JvdXBzIGJ5IHRoZSBkZWZpbmVkIGNvbHVtbnMgYW5kIGRvZXMgdGhlIG9wZXJhdGlvbnMgd2l0aGluIHRob3NlIGdyb3Vwcy4NCkZvciB0aG9zZSBmYW1pbGlhciB3aXRoIHNxbCBpdCB3b3JrcyBwcmV0dHkgbXVjaCB0aGUgc2FtZSBhcyAiZ3JvdXAgYnkiDQoNCmBgYHtyfQ0KI0dyb3VwIGJ5IG9uZSBjb2x1bW4gYW5kIHRoZW4gc3VtbWFyaXNpbmcgd2l0aCB0aG9zZSBncm91cHMNCmRpYW1vbmRzICU+JSBncm91cF9ieShjdXQpICU+JSBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkNCmBgYA0KDQojcGlwaW5nIGNvbWFuZHMgdG9nZXRoZXINClNvIGZhcmUgdGhlIGNvbW1hbmRzIG1vc3RseSBqdXN0IGxvb2tlZCBkaWZmZXJlbnQgdG8gInJlZ3VsYXIiIHItY29tbWFuZHMuIE1heWJlIGFyZSBtb3JlIHN0cmFpZ2h0IGZvcndhcmQuIEJ1dCB0byBleHBlcmllbmNlIChJTU8pIHRoZSByZWFsIGFkdmFudGFnZSBvZiB0aGUgZHBseXItcGFja2FnZSwgeW91IGhhdmUgdG8gc3RhcnQgcGlwaW5nIHRoZSBjb21tYW5kcy4gDQoNCmBgYHtyfQ0KDQojIFNlbGVjdCBhbGwgZGlhbW9uZHMgd2l0aCBhIGZhaXIgY3V0IGFuZCB0aGVuIGNhbHVjYXRlIHRoZSBtZWFuIHByaWNlIGRlcGVuZGluZyBvbiB0aGUgY2xhcml0eQ0KZGlhbW9uZHMgJT4lIGZpbHRlcihjdXQ9PSdGYWlyJykgJT4lIGdyb3VwX2J5KGNsYXJpdHkpICU+JSBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkNCg0KDQpgYGANCg0KDQojIHNvbWUgbW9yZSBoZWxwZnVsbCBmdW5jdGlvbnMNCg0KIyNjYXN0IC8gbWVsdCAtPiBmdW5jdGlvbnMgb2YgdGhlIHJlc2hhcGluZzIgcGFja2FnZQ0KSWYgeW91IGRvbid0IGtub3cgdGhlIGZ1bmN0aW9ucywgY2hlY2sgdGhlbSBvdXQuIFRoZXkgcmVhbGx5IGhlbHAgdG8gcmVvcmdhbml6ZSBkYXRhLg0KPGJyPg0KaW4gdGhpcyBjb250ZXh0ICh0aWR5dmVycy11bml2ZXJzZSkgdGhleSBhcmUgY2FsbGVkIHNwcmVhZCBhbmQgZ2F0aGVyIGFuZCBhcmUgcGFydCBvZiB0aGUgcGFja2FnZSAidGlkeXIiDQpgYGB7cn0NCg0KI3NwcmVhZCB0aGUgcmVzdWx0IA0KDQpkaWFtb25kcyAlPiUgDQogIGZpbHRlcihjdXQhPSdGYWlyJykgJT4lIA0KICBncm91cF9ieShjdXQsY2xhcml0eSkgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkgJT4lIA0KICBzcHJlYWQoa2V5PWN1dCx2YWx1ZT1tZWFuX3ByaWNlKQ0KYGBgDQoNCmBgYHtyfQ0KDQoNCiNyZXZlcnNpbmcgdGhlIHNwcmVhZCB3aXRoIGdhdGhlcg0KZGlhbW9uZHMgJT4lIA0KICBmaWx0ZXIoY3V0IT0nRmFpcicpICU+JSANCiAgZ3JvdXBfYnkoY3V0LGNsYXJpdHkpICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fcHJpY2U9bWVhbihwcmljZSkpICU+JSANCiAgc3ByZWFkKGtleT1jdXQsdmFsdWU9bWVhbl9wcmljZSkgICU+JSANCiAgZ2F0aGVyKGN1dCxwcmljZSwgR29vZDpJZGVhbCApDQogIA0KYGBgDQoNCg0KIyNzaG93IHRoZSBlbnRpcmUgZGF0YS5mcmFtZQ0KZHBseXIgYWx3YXlzIHNob3dzIHRoZSBmaXJzdCAxMCByb3dzLCBidXQgc29tZXRpbWVzIHlvdSBqdXN0IHdhbnQgdG8gc2VlIHRoZW0gYWxsDQoNCmBgYHtyfQ0KI3RvIHNlZSB0aGUgcmVzdWx0IGluIHRoZSBjb25zb2xlLCB1c2UgcHJpbnQobj1ucm93KC4pKQ0KZGlhbW9uZHMgJT4lIA0KICBmaWx0ZXIoY3V0IT0nRmFpcicpICU+JSANCiAgZ3JvdXBfYnkoY3V0LGNsYXJpdHkpICU+JSANCiAgc3VtbWFyaXNlKG1lYW5fcHJpY2U9bWVhbihwcmljZSkpICU+JSANCiAgc3ByZWFkKGtleT1jdXQsdmFsdWU9bWVhbl9wcmljZSkgICU+JSANCiAgZ2F0aGVyKGN1dCxwcmljZSwgR29vZDpJZGVhbCApICU+JSANCiAgcHJpbnQobiA9IG5yb3coLikpDQpgYGANCg0KYGBge3J9DQoNCiNpZiB5b3UgdXNlIFZpZXcoKSBSU3R1ZGlvIG9wZW5zIGEgbmV3IHRhYiB3aXRoIHRoZSBkYXRhDQpkaWFtb25kcyAlPiUgDQogIGZpbHRlcihjdXQhPSdGYWlyJykgJT4lIA0KICBncm91cF9ieShjdXQsY2xhcml0eSkgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9wcmljZT1tZWFuKHByaWNlKSkgJT4lIA0KICBzcHJlYWQoa2V5PWN1dCx2YWx1ZT1tZWFuX3ByaWNlKSAgJT4lIA0KICBnYXRoZXIoY3V0LHByaWNlLCBHb29kOklkZWFsICkgJT4lIA0KICBWaWV3KCkNCg0KDQpgYGANCg0KDQojI3NvbWUgaGVscGZ1bGwgb3RoZXIgdHV0b3JpYWxzDQoNCi0gW0RhdGEgTWFuaXB1bGF0aW9uIHdpdGggZHBseXJdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL2RhdGEtbWFuaXB1bGF0aW9uLXdpdGgtZHBseXIvKQ0KLSBbTGVzc2VyIGtub3duIGRwbHlyIHRyaWNrc10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vbGVzc2VyLWtub3duLWRwbHlyLXRyaWNrcy8pDQotIFtTb21lIHRyaWNrcyBvbiBkcGx5cjo6ZmlsdGVyXShodHRwczovL3NlYmFzdGlhbnNhdWVyLmdpdGh1Yi5pby9kcGx5cl9maWx0ZXIvKQ0KDQoNCg0KSGF2ZSBmdW4gZXhwbG9yaW5nIHlvdXIgZGF0YSENCg0KDQoNCg0KDQo=